/*
 * Copyright (C) Igor Sysoev
 */

#define _XOPEN_SOURCE 600

#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>

#include "include/pf_palloc.h"

/*
 * POOL_MAX_ALLOC_FROM_POOL should be (pagesize - 1), i.e. 4095 on x86.
 * On Windows NT it decreases a number of locked pages in a kernel.
 */
#define ALIGN_MEM(d, a)     (((d) + (a - 1)) & ~(a - 1))

#define ALIGN_PTR(p, a)                                                   \
    (uint8_t *) (((uintptr_t) (p) + ((uintptr_t) a - 1)) & ~((uintptr_t) a - 1))

#define POOL_MAX_ALLOC_FROM_POOL  (4096 - 1)

#define POOL_DEFAULT_POOL_SIZE    (16 * 1024)

#define POOL_ALIGNMENT       16

#define POOL_MIN_POOL_SIZE                                                     \
    ALIGN_MEM((sizeof(pf_pool_t) + 2 * sizeof(pf_pool_large_t)),            \
              POOL_ALIGNMENT)

typedef void (*pf_pool_cleanup_f)(void *data);

struct pf_pool_cleanup
{
    pf_pool_cleanup_f          handler;
    void*                      data;
    struct pf_pool_cleanup*    next;
};

typedef struct pf_pool_large
{
    struct pf_pool_large*    next;
    void*                    alloc;
} pf_pool_large_t;

typedef struct pf_pool_data
{
    uint8_t*              last;
    uint8_t*              end;
    pf_pool_t*            next;
    uint32_t              failed;
} pf_pool_data_t;

struct pf_pool
{
    pf_pool_data_t      d;
    size_t              max;
    pf_pool_t*          current;
    pf_pool_large_t*    large;
    pf_pool_cleanup_t*  cleanup;
};

/*********************************************************************************************
Function Name:  pf_palloc_block
Description  :  Alloc buffer from a new pool's block.
Inputs       :  pf_pool_t* pool   :  Pool pointer. Must be NOT NULL.
                size_t size       :  Buffer size to alloc from pool. Must be more than zero.
Outputs      :  return value      :  (void *)buffer pointer.
                                     NULL. Alloc buffer fail.
ErrorCodes   :  no
History      :
---------------------------------------------------------------------------------------------
Author                 Date                         Comments
lijz                   2011-6-24                    create
**********************************************************************************************/
static void* pf_palloc_block(pf_pool_t *pool, size_t size);

/*********************************************************************************************
Function Name:  pf_palloc_large
Description  :  Alloc buffer from pool's large block.
                While buffer size large than a block size, would alloc it from pool's large block.
Inputs       :  pf_pool_t* pool   :  Pool pointer. Must be NOT NULL.
                size_t size       :  Buffer size to alloc from pool. Must be more than zero.
Outputs      :  return value      :  (void *)buffer pointer.
                                     NULL. Alloc buffer fail.
ErrorCodes   :  no
History      :
---------------------------------------------------------------------------------------------
Author                 Date                         Comments
lijz                   2011-6-24                    create
**********************************************************************************************/
static void* pf_palloc_large(pf_pool_t *pool, size_t size);

/********************************************************************************************/

pf_pool_t* pf_pool_create(size_t size)
{
    pf_pool_t* p = NULL;

    size = (size > POOL_MIN_POOL_SIZE) ? size : POOL_MIN_POOL_SIZE;

    if(posix_memalign((void **)&p, POOL_ALIGNMENT, size))
    {
        return NULL;
    }

    p->d.last   = (uint8_t *) p + sizeof(pf_pool_t);
    p->d.end    = (uint8_t *) p + size;
    p->d.next   = NULL;
    p->d.failed = 0;

    size   = size - sizeof(pf_pool_t);
    p->max = (size < POOL_MAX_ALLOC_FROM_POOL) ? size : POOL_MAX_ALLOC_FROM_POOL;

    p->current = p;
    p->large   = NULL;
    p->cleanup = NULL;

    return p;
}

void pf_pool_destroy(pf_pool_t* pool)
{
    pf_pool_t* p         = NULL;
    pf_pool_t* n         = NULL;
    pf_pool_large_t* l   = NULL;
    pf_pool_cleanup_t* c = NULL;

    for(c = pool->cleanup; c; c = c->next)
    {
        if(c->handler)
        {
            c->handler(c->data);
        }
    }

    for(l = pool->large; l; l = l->next)
    {

        if(l->alloc)
        {
            free(l->alloc);
        }
    }

    for(p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next)
    {
        free(p);

        if (n == NULL)
        {
            break;
        }
    }

    return;
}

void pf_pool_reset(pf_pool_t* pool)
{
    pf_pool_t* p       = NULL;
    pf_pool_t* next    = NULL;
    pf_pool_large_t* l = NULL;

    for(l = pool->large; l; l = l->next)
    {
        if(l->alloc)
        {
            free(l->alloc);
        }
    }

    pool->large = NULL;

    p = pool;

    /* Modify by hhq, only the first block offset pf_pool_t */
    p->d.last = (uint8_t *) p + sizeof(pf_pool_t);

    for(next = p->d.next; next ; )
    {
        p    = next;
        next = next->d.next;
        free(p);
    }

    pool->d.next    = NULL;
    pool->d.failed  = 0;
    pool->current   = pool;

    return;
}

void* pf_palloc(pf_pool_t* pool, size_t size)
{
    uint8_t* m   = NULL;
    pf_pool_t* p = NULL;

    if(size <= 0)
    {
        return NULL;
    }

    if(size <= pool->max)
    {

        p = pool->current;

        do
        {
            m = ALIGN_PTR(p->d.last, sizeof(unsigned long));

            if((size_t)(p->d.end - m) >= size)
            {
                p->d.last = m + size;

                return m;
            }

            p = p->d.next;

        }while(p);

        return pf_palloc_block(pool, size);
    }

    return pf_palloc_large(pool, size);
}

void* pf_pnalloc(pf_pool_t* pool, size_t size)
{
    uint8_t*  m  = NULL;
    pf_pool_t* p = NULL;

    if(size <= 0)
    {
        return NULL;
    }

    if(size <= pool->max)
    {

        p = pool->current;

        do
        {
            m = p->d.last;

            if((size_t)(p->d.end - m) >= size)
            {
                p->d.last = m + size;

                return m;
            }

            p = p->d.next;

        }while(p);

        return pf_palloc_block(pool, size);
    }

    return pf_palloc_large(pool, size);
}

void* pf_pcalloc(pf_pool_t* pool, size_t size)
{
    void *p = NULL;

    p = pf_palloc(pool, size);
    if(p)
    {
        memset(p,0,size);
    }

    return p;
}

void* pf_pmemalign(pf_pool_t* pool, size_t size, size_t alignment)
{
    void* p                = NULL;
    pf_pool_large_t* large = NULL;

    if(posix_memalign(&p, alignment, size))
    {
        return NULL;
    }

    large = pf_palloc(pool, sizeof(pf_pool_large_t));
    if(large == NULL)
    {
        free(p);
        return NULL;
    }

    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

int32_t pf_pfree(pf_pool_t* pool, void* p)
{
    pf_pool_large_t* l = NULL;

    for(l = pool->large; l; l = l->next)
    {
        if(p == l->alloc)
        {
            free(l->alloc);
            l->alloc = NULL;

            return 0;
        }
    }

    return 0;
}

pf_pool_cleanup_t* pf_pool_cleanup_add(pf_pool_t* p, size_t size)
{
    pf_pool_cleanup_t* c = NULL;

    c = pf_palloc(p, sizeof(pf_pool_cleanup_t));
    if(c == NULL)
    {
        return NULL;
    }

    if(size > 0)
    {
        c->data = pf_palloc(p, size);
        if(c->data == NULL)
        {
            return NULL;
        }

    }
    else
    {
        c->data = NULL;
    }

    c->handler = NULL;
    c->next = p->cleanup;

    p->cleanup = c;

    return c;
}

/********************************************************************************************/

static void* pf_palloc_block(pf_pool_t* pool, size_t size)
{
    uint8_t* m         = NULL;
    size_t   psize     = -1;
    pf_pool_t* p       = NULL;
    pf_pool_t* new     = NULL;
    pf_pool_t* current = NULL;

    psize = (size_t)(pool->d.end - (uint8_t *) pool);

    if(posix_memalign((void **)&m, POOL_ALIGNMENT, psize))
    {
        return NULL;
    }

    new = (pf_pool_t *)m;

    new->d.end    = m + psize;
    new->d.next   = NULL;
    new->d.failed = 0;

    m          += sizeof(pf_pool_data_t);
    m           = ALIGN_PTR(m, sizeof(unsigned long));
    new->d.last = m + size;

    current = pool->current;

    for(p = current; p->d.next; p = p->d.next)
    {
        if(p->d.failed++ > 4)
        {
            current = p->d.next;
        }
    }

    p->d.next = new;

    pool->current = current ? current : new;

    return m;
}

static void* pf_palloc_large(pf_pool_t* pool, size_t size)
{
    void* p                = NULL;
    uint32_t n             = 0;
    pf_pool_large_t* large = NULL;

    p = malloc(size);
    if(p == NULL)
    {
        return NULL;
    }

    n = 0;

    for(large = pool->large; large; large = large->next)
    {
        if(large->alloc == NULL)
        {
            large->alloc = p;
            return p;
        }

        if(n++ > 3)
        {
            break;
        }
    }

    large = pf_palloc(pool, sizeof(pf_pool_large_t));
    if(large == NULL)
    {
        free(p);
        return NULL;
    }

    large->alloc = p;
    large->next = pool->large;
    pool->large = large;

    return p;
}

